package com.yonyou.pmclouds.plantemplate.controller;

import cn.afterturn.easypoi.excel.ExcelImportUtil;
import cn.afterturn.easypoi.excel.entity.ImportParams;
import cn.afterturn.easypoi.excel.entity.result.ExcelImportResult;
import com.alibaba.dubbo.config.annotation.Reference;
import com.yonyou.pmclouds.ApplicationConsts;
import com.yonyou.pmclouds.basecom.exception.BusinessException;
import com.yonyou.pmclouds.basecom.runtime.RuntimeEnvironment;
import com.yonyou.pmclouds.billtype.util.BillTypeInfoQueryUtils;
import com.yonyou.pmclouds.billtype.util.DepartmentInfoQueryUtils;
import com.yonyou.pmclouds.milestone.entity.MilestoneVO;
import com.yonyou.pmclouds.milestone.service.IMilestoneQuery;
import com.yonyou.pmclouds.outter.user.UserInfoQuery;
import com.yonyou.pmclouds.plan.entity.ProjectPlanImportVO;
import com.yonyou.pmclouds.plantemplate.entity.ScheduleTemplateItemVO;
import com.yonyou.pmclouds.plantemplate.entity.ScheduleTemplateVO;
import com.yonyou.pmclouds.plantemplate.entity.ScheduleTemplateViewVO;
import com.yonyou.pmclouds.plantemplate.rmiitf.IScheduleTemplateQueryService;
import com.yonyou.pmclouds.plantemplate.rmiitf.IScheduleTemplateService;
import com.yonyou.pmclouds.user.entity.UserInfo;
import com.yonyou.pmclouds.user.entity.UserSearchResultVO;
import lombok.extern.slf4j.Slf4j;
import net.sf.mpxj.MPXJException;
import net.sf.mpxj.ProjectFile;
import net.sf.mpxj.Relation;
import net.sf.mpxj.Task;
import net.sf.mpxj.mpp.MPPReader;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.io.InputStream;
import java.util.*;

/**
 * @Description: 进度计划模板WEB端
 * @Author: zhangzhonghao
 * @Date: 2020-03-06
 */

@Slf4j
@RestController
@RequestMapping("/schedule/template")
public class ScheduleTemplateController {

    @Reference(version = ApplicationConsts.APPLICATION_VERSION)
    private IScheduleTemplateService templateService;

    @Reference(version = ApplicationConsts.APPLICATION_VERSION)
    private IScheduleTemplateQueryService templateQueryService;

    @Reference(version = ApplicationConsts.APPLICATION_VERSION)
    private IMilestoneQuery milestoneQuery;

    @Autowired
    private BillTypeInfoQueryUtils billTypeInfoQueryUtils;

    @Autowired
    private DepartmentInfoQueryUtils departmentInfoQueryUtils;

    @Autowired
    private UserInfoQuery userInfoQuery;

    /**
     * 计划模板保存接口
     * @param templateVO 计划模板VO
     * @return 计划模板
     */
    @RequestMapping("/save")
    public ScheduleTemplateVO insert(@RequestBody ScheduleTemplateVO templateVO) throws BusinessException {
        return templateService.insert(templateVO);
    }

    /**
     * 计划模板删除接口
     * @param templateVO 计划模板VO
     * @return 计划模板VO
     */
    @RequestMapping("/delete")
    public ScheduleTemplateVO delete(@RequestBody ScheduleTemplateVO templateVO) throws BusinessException {
        return templateService.delete(templateVO);
    }

    /**
     * 通过计划模板主键查询计划详情接口，包含子表与关联项的值
     * @param pkScheduleTemplate 计划模板主键
     * @return 计划VO
     */
    @RequestMapping("/queryById")
    public ScheduleTemplateVO queryByPk(@RequestParam("pkScheduleTemplate") String pkScheduleTemplate) throws BusinessException {
        ScheduleTemplateVO templateVO = templateQueryService.queryById(pkScheduleTemplate);
        if (templateVO == null) {
            return null;
        }

        setRelatedValue(new ScheduleTemplateVO[]{templateVO});

        return templateVO;
    }

    @RequestMapping("/search")
    public List<ScheduleTemplateViewVO> search(@RequestParam("keyword") String keyword) throws BusinessException {
        ScheduleTemplateVO[] templateVOS = templateQueryService.searchTemplate(keyword);
        if (templateVOS == null || templateVOS.length <= 0) {
            return null;
        }

        setRelatedValue(templateVOS);

        Map<String, List<ScheduleTemplateVO>> typeTempMap = new HashMap<>();
        // 计划类型主键和名称的映射
        Map<String, String> typeNameMap = new HashMap<>();
        for(ScheduleTemplateVO vo : templateVOS){
            List<ScheduleTemplateVO> templateVOList = typeTempMap.get(vo.getType());
            if(templateVOList == null){
                templateVOList = new ArrayList<>();
                typeTempMap.put(vo.getType(), templateVOList);
            }
            templateVOList.add(vo);
            typeNameMap.put(vo.getType(), vo.getTypeName());
        }

        List<ScheduleTemplateViewVO> templateViewVOS = new ArrayList<>();
        for(Map.Entry<String, List<ScheduleTemplateVO>> entry : typeTempMap.entrySet()){
            ScheduleTemplateViewVO viewVO = new ScheduleTemplateViewVO();
            viewVO.setType(entry.getKey());
            viewVO.setTypeName(typeNameMap.get(entry.getKey()) == null ? entry.getKey() : typeNameMap.get(entry.getKey()));
            List<ScheduleTemplateVO> templateVOList = typeTempMap.get(entry.getKey());
            if(templateVOList != null && templateVOList.size() > 0){
                viewVO.setTemplateVOS(templateVOList.toArray(new ScheduleTemplateVO[]{}));
            }
            templateViewVOS.add(viewVO);
        }

        return templateViewVOS;
    }

    /**
     * 获取负责人集合（当前租户下的用户）
     */
    @GetMapping("/searchUser")
    @ResponseBody
    public UserInfo[] searchUser(@RequestParam(value = "keyword", required = false) String keyword) {
        UserSearchResultVO.ContentVO users = userInfoQuery.searchUsers(RuntimeEnvironment.getTenantId(), keyword,
                1, 1000);
        if (users == null)
            return new UserInfo[0];

        return users.getContent();
    }

    @RequestMapping("/importFromExcel")
    public ScheduleTemplateItemVO[] importFromExcel(@RequestParam(value="file") MultipartFile file, ScheduleTemplateVO templateVO) throws BusinessException {
        checkImportFromExcel(file, templateVO);

        //解析Excel
        List<ProjectPlanImportVO> planList = analyzeExcel(file);
        if(planList == null || planList.size() <= 0){
            return new ScheduleTemplateItemVO[0];
        }

        // 翻译关联项的值
        setExcelRelateValue(planList);

        // 转换为任务项VO
        setImportItems(planList, templateVO);

        return templateVO.getScheduleTemplateItemVOS();
    }

    @PostMapping(value = "/importProject")
    @ResponseBody
    public ScheduleTemplateItemVO[] importProject(ScheduleTemplateVO templateVO, @RequestParam(value="file") MultipartFile file) throws BusinessException {
        checkProjectFile(templateVO, file);

        InputStream in = null;
        try{
            in = file.getInputStream();
            MPPReader mppRead = new MPPReader();
            ProjectFile pf = mppRead.read(in);
            List<ScheduleTemplateItemVO> itemVOS = coverItemsByTask(pf.getChildTasks(), templateVO);
            return itemVOS.toArray(new ScheduleTemplateItemVO[]{});
        } catch (MPXJException | IOException e) {
            log.error("project文件解析失败" + e);
            throw new BusinessException("project文件解析失败:" + e.getMessage());
        } finally {
            if(in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    log.error("project模版导入关闭输入流失败: " + e);
                }
            }
        }
    }

    private void checkProjectFile(ScheduleTemplateVO templateVO, MultipartFile file) throws BusinessException {
        if(templateVO == null) {
            throw new BusinessException("根计划不能为空！");
        }

        if(file == null) {
            throw new BusinessException("导入文件为空！");
        }
        //校验文件后缀
        String fileName = file.getOriginalFilename();
        String suffix = fileName.substring(fileName.lastIndexOf(".") + 1);
        if(!suffix.equals("mpp")){
            throw new BusinessException("文件格式错误");
        }
    }

    private List<ScheduleTemplateItemVO> coverItemsByTask(List<Task> tasks, ScheduleTemplateVO templateVO) throws BusinessException {
        List<ScheduleTemplateItemVO> rootItemVOS = new ArrayList<>();
        Map<String, String> taskIdWithCodeMap = new HashMap<>();
        if(tasks == null || tasks.size() <= 0){
            return rootItemVOS;
        }

        List<Task> rootTaskList = tasks.get(0).getChildTasks();
        if(rootTaskList == null || rootTaskList.size() <= 0){
            return rootItemVOS;
        }

        for(int i = 0; i < rootTaskList.size(); i++){
            Task rootTask = rootTaskList.get(i);
            ScheduleTemplateItemVO rootItemVO = coverItemByTask(rootTask, templateVO, String.valueOf(i + 1), taskIdWithCodeMap);
            rootItemVOS.add(rootItemVO);
        }

        coverDependTaskIdToCode(rootItemVOS.toArray(new ScheduleTemplateItemVO[]{}), taskIdWithCodeMap);

        setNewCode(templateVO, rootItemVOS);

        return rootItemVOS;

    }

    private void coverDependTaskIdToCode(ScheduleTemplateItemVO[] itemVOS, Map<String, String> taskIdWithCodeMap){
        if(itemVOS == null || itemVOS.length <= 0){
            return;
        }
        for(ScheduleTemplateItemVO itemVO : itemVOS){
            String dependCodeName = itemVO.getDependPlanName();
            if(StringUtils.isNotEmpty(dependCodeName)){
                itemVO.setDependPlanName(taskIdWithCodeMap.get(dependCodeName));
            }
            coverDependTaskIdToCode(itemVO.getChildren(), taskIdWithCodeMap);
        }


    }

    private ScheduleTemplateItemVO coverItemByTask(Task task, ScheduleTemplateVO templateVO, String code, Map<String, String> taskIdWithCodeMap) throws BusinessException {
        ScheduleTemplateItemVO itemVO = new ScheduleTemplateItemVO();
        itemVO.setCode(code);
        int seq = code.contains(".") ? Integer.parseInt(code.substring(code.lastIndexOf(".") + 1)) : Integer.parseInt(code);
        itemVO.setSeq(seq);
        itemVO.setName(task.getName());
        int planDays = task.getDuration() != null ? (int)task.getDuration().getDuration() : 1;
        itemVO.setPlanDays(planDays);
        taskIdWithCodeMap.put(String.valueOf(task.getID()), code);
        List<Relation> relations = task.getPredecessors();
        if(relations != null && relations.size() > 0){
            // 目前计划编制前置计划只支持一个
            Relation relation = relations.get(0);
            itemVO.setDependPlanName(String.valueOf(relation.getTargetTask().getID()));
        }

        List<Task> childTaskList = task.getChildTasks();
        if(childTaskList != null && childTaskList.size() > 0){
            List<ScheduleTemplateItemVO> childItemVOS = new ArrayList<>();
            for(int i = 0; i < childTaskList.size(); i++){
                Task childTask = childTaskList.get(i);
                String childCode = code + "." + (i + 1);
                ScheduleTemplateItemVO childItemVO = coverItemByTask(childTask, templateVO, childCode, taskIdWithCodeMap);
                childItemVOS.add(childItemVO);
            }

            if(childItemVOS.size() > 0){
                itemVO.setChildren(childItemVOS.toArray(new ScheduleTemplateItemVO[]{}));
                itemVO.setChildCount(childItemVOS.size());
            }
        }

        return itemVO;
    }

    private void setImportItems(List<ProjectPlanImportVO> planList, ScheduleTemplateVO templateVO){
        List<ScheduleTemplateItemVO> itemVOS = new ArrayList<>();
        for(ProjectPlanImportVO importVO : planList){
            ScheduleTemplateItemVO itemVO = new ScheduleTemplateItemVO();
            itemVO.setCode(importVO.getScode());
            itemVO.setName(importVO.getSname());
            itemVO.setPlanDays(importVO.getExpeDuration());
            itemVO.setPkMilestone(importVO.getPkMilestone());
            itemVO.setMilestoneName(importVO.getMilestoneName());
            itemVO.setMilestoneCode(importVO.getMilestoneCode());
            itemVO.setDependPlanName(importVO.getDependPlanName());

            itemVOS.add(itemVO);
        }

        // 构造树结构，并返回根节点信息
        List<ScheduleTemplateItemVO> rootItemList = this.getItemOnlyRoot(itemVOS);

        // 如果存在手动创建的任务项，则导入的放在已创建之后，编码重排
        setNewCode(templateVO, rootItemList);

        if(rootItemList.size() > 0){
            templateVO.setScheduleTemplateItemVOS(rootItemList.toArray(new ScheduleTemplateItemVO[]{}));
        }
    }

    private void setNewCode(ScheduleTemplateVO templateVO, List<ScheduleTemplateItemVO> rootItemList ){
        int childCount = templateVO.getChildCount();
        if(childCount <= 0){
            return;
        }

        Map<String, String> newWithOldCodeMap = new HashMap<>();
        for(ScheduleTemplateItemVO itemVO : rootItemList){
            String code = itemVO.getCode();
            String newCode = String.valueOf(Integer.parseInt(code) + childCount);
            newWithOldCodeMap.put(code, newCode);
            itemVO.setCode(newCode);
            itemVO.setSeq(Integer.parseInt(newCode));
            setChildNewCode(newWithOldCodeMap, itemVO);
        }

        setNewDependCode(newWithOldCodeMap, rootItemList);
    }

    private void setNewDependCode(Map<String, String> newWithOldCodeMap, List<ScheduleTemplateItemVO> rootItemList){
        for(ScheduleTemplateItemVO itemVO : rootItemList){
            setChildNewDependCode(newWithOldCodeMap, itemVO);
        }
    }

    private void setChildNewDependCode(Map<String, String> newWithOldCodeMap, ScheduleTemplateItemVO parentItemVO){
        if(StringUtils.isNotEmpty(parentItemVO.getDependPlanName())){
            parentItemVO.setDependPlanName(newWithOldCodeMap.get(parentItemVO.getDependPlanName()));
        }

        if(parentItemVO.getChildren() == null || parentItemVO.getChildren().length <= 0){
            return;
        }

        for(ScheduleTemplateItemVO childItemVO : parentItemVO.getChildren()){
            setChildNewDependCode(newWithOldCodeMap, childItemVO);
        }
    }

    private void setChildNewCode(Map<String, String> newWithOldCodeMap, ScheduleTemplateItemVO parentItemVO){
        if(parentItemVO.getChildren() == null || parentItemVO.getChildren().length <= 0){
            return;
        }

        String parentCode = parentItemVO.getCode();
        ScheduleTemplateItemVO[] childItems = parentItemVO.getChildren();
        for(ScheduleTemplateItemVO childItemVO : childItems){
            String childCode = childItemVO.getCode();
            String newChildCode = parentCode + childCode.substring(parentCode.length());
            newWithOldCodeMap.put(childCode, newChildCode);
            childItemVO.setCode(newChildCode);
            setChildNewCode(newWithOldCodeMap, childItemVO);
        }

    }

    private List<ScheduleTemplateItemVO> getItemOnlyRoot(List<ScheduleTemplateItemVO> itemVOS){
        this.genItemTree(itemVOS);
        List<ScheduleTemplateItemVO> rootItemList = new ArrayList<>();
        for(ScheduleTemplateItemVO itemVO : itemVOS){
            if(!itemVO.getCode().contains(".")){
                rootItemList.add(itemVO);
            }
        }
        return rootItemList;
    }

    private void genItemTree(List<ScheduleTemplateItemVO> itemVOS){
        Map<String, List<ScheduleTemplateItemVO>> itemMap = new HashMap<>();
        for(ScheduleTemplateItemVO itemVO : itemVOS){
            String key = itemVO.getCode();
            if(!key.contains(".")){
                continue;
            }

            key = key.substring(0, key.lastIndexOf("."));
            List<ScheduleTemplateItemVO> itemVOList = itemMap.get(key);
            if(itemVOList == null){
                itemVOList = new ArrayList<>();
                itemMap.put(key, itemVOList);
            }

            itemVOList.add(itemVO);
        }

        for(ScheduleTemplateItemVO itemVO : itemVOS){
            String key = itemVO.getCode();
            List<ScheduleTemplateItemVO> childItems = itemMap.get(key);
            if(childItems != null && childItems.size() > 0){
                itemVO.setChildren(childItems.toArray(new ScheduleTemplateItemVO[]{}));
            }
        }
    }

    private void checkImportFromExcel(MultipartFile file, ScheduleTemplateVO templateVO) throws BusinessException{
        if(templateVO == null){
            throw new BusinessException("计划编制不能为空！");
        }

        if(file == null){
            throw new BusinessException("导入文件为空！");
        }

        String fileOriName = file.getOriginalFilename();
        if(StringUtils.isEmpty(fileOriName)){
            throw new BusinessException("文件信息不全！");
        }else if(!fileOriName.endsWith(".xls") && !fileOriName.endsWith(".xlsx")){
            throw new BusinessException("导入文件不是Excel格式！");
        }
    }

    private List<ProjectPlanImportVO> analyzeExcel(MultipartFile file) throws BusinessException {
        //导入Excel
        ImportParams params = new ImportParams();
        params.setTitleRows(0);
        params.setHeadRows(1);
        params.setNeedVerfiy(true);
        ExcelImportResult<ProjectPlanImportVO> result;
        InputStream inputStream = null;
        try {
            inputStream = file.getInputStream();
            result = ExcelImportUtil.importExcelMore(inputStream, ProjectPlanImportVO.class, params);
        } catch (Exception e) {
            log.error("计划编制导入Excel失败：" + e);
            throw new BusinessException("计划编制导入Excel失败：" + e.getMessage());
        }finally {
            if(inputStream != null){
                try {
                    inputStream.close();
                } catch (IOException e) {
                    log.error("计划编制Excel导入关闭输入流失败：" + e);
                }
            }
        }

        if(result == null){
            return new ArrayList<>();
        }

        if(!result.getFailList().isEmpty()){
            StringBuilder errorInfoBuilder = new StringBuilder("如下计划项格式编写错误：[");

            for(Object object : result.getFailList()){
                ProjectPlanImportVO importVO = (ProjectPlanImportVO) object;
                errorInfoBuilder.append(importVO.getSname()).append(",");
            }

            String errorInfo = errorInfoBuilder.toString();
            errorInfo = errorInfo.substring(0, errorInfo.length() - 1) + "],请按限定格式导入。";
            throw new BusinessException(errorInfo);
        }
        return result.getList();
    }

    private void setExcelRelateValue(List<ProjectPlanImportVO> planList) throws BusinessException {
        Set<String> milestoneCodeSet = new HashSet<>();
        Set<String> managerNameSet = new HashSet<>();

        for(ProjectPlanImportVO importVO : planList){
            milestoneCodeSet.add(importVO.getMilestoneCode());
            managerNameSet.add(importVO.getManagerName());
        }

        // 查询里程碑档案
        Map<String, MilestoneVO> milestoneVOMap = milestoneQuery.queryByCodes(milestoneCodeSet.toArray(new String[0]),
                RuntimeEnvironment.getTenantId());

        for (ProjectPlanImportVO importVO : planList) {
            if (StringUtils.isNotEmpty(importVO.getMilestoneCode())){
                MilestoneVO milestoneVO = milestoneVOMap.get(importVO.getMilestoneCode());
                if (milestoneVO != null){
                    importVO.setPkMilestone(milestoneVO.getPkMilestone());
                    importVO.setMilestoneName(milestoneVO.getName());
                }
            }
        }

    }

    /**
     * 翻译关联项的值
     * @param templateVOS 计划
     */
    private void setRelatedValue(ScheduleTemplateVO[] templateVOS) throws BusinessException {
        Set<String> billTypeCodeSet = new HashSet<>();
        Set<String> pkDeptSet = new HashSet<>();
        List<ScheduleTemplateItemVO> itemVOS = new ArrayList<>();
        for (ScheduleTemplateVO templateVO : templateVOS) {
            for (ScheduleTemplateItemVO itemVO : templateVO.getScheduleTemplateItemVOS()) {
                getRelatedItem(itemVO, billTypeCodeSet, pkDeptSet, itemVOS);
            }
        }

        Map<String, String> billTypeNameMap = billTypeInfoQueryUtils.queryNameByType(
                billTypeCodeSet.toArray(new String[]{}));
        Map<String, String> departmentNameMap = departmentInfoQueryUtils.queryNameByPks(
                pkDeptSet.toArray(new String[]{}));

        for (ScheduleTemplateVO scheduleVO : templateVOS) {
            for (ScheduleTemplateItemVO itemVO : scheduleVO.getScheduleTemplateItemVOS()) {
                setRelatedItemValue(itemVO, billTypeNameMap, departmentNameMap);
            }
        }

        String[] itemUserFields = new String[]{ScheduleTemplateItemVO.MANAGER, ScheduleTemplateItemVO.CREATOR};
        String[] itemUserNameFields = new String[]{ScheduleTemplateItemVO.MANAGER_NAME, ScheduleTemplateItemVO.CREATOR_NAME};
        userInfoQuery.tranUserInfoField(itemVOS.toArray(new ScheduleTemplateItemVO[]{}), itemUserFields, itemUserNameFields);

        String[] scheduleUserFields = new String[]{ScheduleTemplateVO.CREATOR};
        String[] scheduleUserNameFields = new String[]{ScheduleTemplateVO.CREATOR_NAME};
        userInfoQuery.tranUserInfoField(templateVOS, scheduleUserFields, scheduleUserNameFields);
    }



    /**
     * 获取需要翻译的关联项
     *
     * @param itemVO      任务节点
     * @param billTypeSet 任务关联的单据类型
     * @param pkDeptSet   任务关联的责任部门
     * @param itemVOS     所有的子级，翻译人员名称使用
     */
    private void getRelatedItem(ScheduleTemplateItemVO itemVO, Set<String> billTypeSet, Set<String> pkDeptSet, List<ScheduleTemplateItemVO> itemVOS) {
        itemVOS.add(itemVO);

        if (StringUtils.isNotEmpty(itemVO.getRelatedBillType())) {
            billTypeSet.add(itemVO.getRelatedBillType());
        }

        if (StringUtils.isNotEmpty(itemVO.getPkDepartment())) {
            pkDeptSet.add(itemVO.getPkDepartment());
        }

        if (itemVO.getChildren() != null && itemVO.getChildren().length > 0) {
            for (ScheduleTemplateItemVO childItemVO : itemVO.getChildren()) {
                getRelatedItem(childItemVO, billTypeSet, pkDeptSet, itemVOS);
            }
        }
    }

    /**
     * 对关联项进行翻译赋值
     *
     * @param itemVO            任务项
     * @param billTypeNameMap   关联单据类型描述汇总
     * @param departmentNameMap 关联部门描述汇总
     */
    private void setRelatedItemValue(ScheduleTemplateItemVO itemVO, Map<String, String> billTypeNameMap, Map<String, String> departmentNameMap) {
        if (StringUtils.isNotEmpty(itemVO.getRelatedBillType())) {
            itemVO.setRelatedBillTypeName(billTypeNameMap.get(itemVO.getRelatedBillType()));
        }
        if (StringUtils.isNotEmpty(itemVO.getPkDepartment())) {
            itemVO.setDepartmentName(departmentNameMap.get(itemVO.getPkDepartment()));
        }

        if (itemVO.getChildren() != null && itemVO.getChildren().length > 0) {
            for (ScheduleTemplateItemVO childItemVO : itemVO.getChildren()) {
                setRelatedItemValue(childItemVO, billTypeNameMap, departmentNameMap);
            }
        }
    }
}
